package com.zjj.lbw.algorithm.adt;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author zhanglei.zjj
 * @description leetcode_399. 除法求值
 * 给你一个变量对数组 equations 和一个实数值数组 values 作为已知条件，其中 equations[i] = [Ai, Bi] 和 values[i] 共同表示等式 Ai / Bi = values[i] 。每个 Ai 或 Bi 是一个表示单个变量的字符串。
 * <p>
 * 另有一些以数组 queries 表示的问题，其中 queries[j] = [Cj, Dj] 表示第 j 个问题，请你根据已知条件找出 Cj / Dj = ? 的结果作为答案。
 * <p>
 * 返回 所有问题的答案 。如果存在某个无法确定的答案，则用 -1.0 替代这个答案。如果问题中出现了给定的已知条件中没有出现的字符串，也需要用 -1.0 替代这个答案。
 * <p>
 * 注意：输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况，且不存在任何矛盾的结果。
 * <p>
 * <p>
 * <p>
 * 示例 1：
 * <p>
 * 输入：equations = [["a","b"],["b","c"]], values = [2.0,3.0], queries = [["a","c"],["b","a"],["a","e"],["a","a"],["x","x"]]
 * 输出：[6.00000,0.50000,-1.00000,1.00000,-1.00000]
 * 解释：
 * 条件：a / b = 2.0, b / c = 3.0
 * 问题：a / c = ?, b / a = ?, a / e = ?, a / a = ?, x / x = ?
 * 结果：[6.0, 0.5, -1.0, 1.0, -1.0 ]
 * 示例 2：
 * <p>
 * 输入：equations = [["a","b"],["b","c"],["bc","cd"]], values = [1.5,2.5,5.0], queries = [["a","c"],["c","b"],["bc","cd"],["cd","bc"]]
 * 输出：[3.75000,0.40000,5.00000,0.20000]
 * 示例 3：
 * <p>
 * 输入：equations = [["a","b"]], values = [0.5], queries = [["a","b"],["b","a"],["a","c"],["x","y"]]
 * 输出：[0.50000,2.00000,-1.00000,-1.00000]
 * <p>
 * <p>
 * 提示：
 * <p>
 * 1 <= equations.length <= 20
 * equations[i].length == 2
 * 1 <= Ai.length, Bi.length <= 5
 * values.length == equations.length
 * 0.0 < values[i] <= 20.0
 * 1 <= queries.length <= 20
 * queries[i].length == 2
 * 1 <= Cj.length, Dj.length <= 5
 * Ai, Bi, Cj, Dj 由小写英文字母与数字组成
 * @date 2023/6/13 21:24
 */
public class EvaluateDivision_leetcode_399 {
    public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
        int equationSize = equations.size();
        L399_ADT adt = new L399_ADT(2 * equationSize);

        // 拆解equations中的变量对，给每个变量赋一个单独的id，方便并查集操作
        Map<String, Integer> map = new HashMap<>(2 * equationSize);
        int id = 0;
        for (int i = 0; i < equationSize; i++) {
            List<String> equation = equations.get(i);
            String first = equation.get(0);
            String second = equation.get(1);
            if (!map.containsKey(first)) {
                map.put(first, id);
                id++;
            }
            if (!map.containsKey(second)) {
                map.put(second, id);
                id++;
            }
            // 对变量对中的两个变量进行集合合并
            adt.union(map.get(first), map.get(second), values[i]);
        }

        // 处理问题数组queries
        int queriesSize = queries.size();
        double[] res = new double[queriesSize];
        for (int i = 0; i < queriesSize; i++) {
            String var1 = queries.get(i).get(0);
            String var2 = queries.get(i).get(1);

            Integer id1 = map.get(var1);
            Integer id2 = map.get(var2);

            if (id1 == null || id2 == null) {
                res[i] = -1.0d;
            } else {
                res[i] = adt.calc(id1, id2);
            }
        }
        return res;

    }
}

// 并查集
class L399_ADT {

    // 记录每个元素的父节点
    private int[] parent;

    // 结点指向当前父节点时的权值，因为存在路径压缩，
    // 所以父节点会变化，权值自然也会变化
    private double[] weight;

    public L399_ADT(int n) {
        this.parent = new int[n];
        this.weight = new double[n];
        for (int i = 0; i < n; i++) {
            // 初始化，每个元素都是自己的的父节点，权值为1
            parent[i] = i;
            weight[i] = 1.0d;
        }
    }

    // 集合合并，并更新权值
    public void union(int x, int y, double value) {
        int rootX = find(x);
        int rootY = find(y);
        // 两者不属于同一集合，合并，并更新权值
        if (rootX != rootY) {
            parent[rootX] = rootY;
            weight[rootX] = weight[y] * value / weight[x];
        }
    }

    // 带路径压缩的根结点查找，并更新权值
    public int find(int x) {
        if (x != parent[x]) {
            int origin = parent[x];
            parent[x] = find(parent[x]);
            weight[x] = weight[x] * weight[origin];
        }
        return parent[x];
    }

    public double calc(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return weight[x] / weight[y];
        } else {
            return -1.0d;
        }
    }
}
